<?php
abstract class Dmf_Form_Element
{
    /**
     * All validator objects
     * 
     * @var array
     */
    private $_validators = array();
    
    /**
     * The order of the element
     * 
     * @var int 
     */
    private $_order;
    
    /**
     * Element attributes
     * 
     * @var array
     */
    protected $_attributes = array();
    
    /**
     * The label element for this element
     * 
     * @var Dmf_Form_Element_Label 
     */
    private $_label;
    
    /**
     * All error objects
     * 
     * @var array
     */
    private $_errors = array();
    
    /**
     * Decorator elements
     * 
     * @var array
     */
    private $_decorators = array();
    
    /**
     * Custom element validation error
     * 
     * @var null|string 
     */
    private $_customError = null;
    
    /**
     * The form handler
     * 
     * @var Dmf_Form
     */
    public $form;
    
    /**
     * Prepend text
     * 
     * @var string 
     */
    private $_prependText;
    
    /**
     * All element filters
     * 
     * @var array
     */
    private $_filters = array();
    
    /**
     * All element helpers
     * 
     * @var array
     */
    private $_helpers = array();
    
    /**
     * Append text
     * 
     * @var string 
     */
    private $_appendText;
    
    /**
     * All error attributes
     * 
     * @var array
     */
    private $_errorAttributes;
    
    /**
     * Delete this element 
     */
    public function delete()
    {
        $this->form->deleteElement( $this );
    }
    
    /**
     * Prepend text
     * 
     * @param string $text 
     */
    public function prependText( $text )
    {
        $this->_prependText = $text;
        
        return $this;
    }
    
    /**
     * Append text
     * 
     * @param string $text 
     */
    public function appendText( $text )
    {
        $this->_appendText = $text;
        
        return $this;
    }
    
    /**
     * Set the order of the element
     * 
     * @param int $order 
     * @return Dmf_Form_Element
     */
    public function order( $order )
    {
        $i = 1;
        $elements = $this->form->getElements();
        $this->_order = $order;
        
        foreach( $elements as $element )
        {
            if ( $element == $this ) continue;
            
            if ( $element->getOrder() >= $order )
            {
                $element->setOrder( ( $order + $i ) );
                $i++;
            }
        }
        
        return $this;
    }
    
    /**
     * Set the order withouth recursion
     * 
     * @param int $order 
     */
    public function setOrder( $order )
    {
        $this->_order = $order;
    }
    
    /**
     * Get order number
     * 
     * @return int
     */
    public function getOrder()
    {
        return $this->_order;
    }
    
    /**
     * Display custom error message
     * 
     * @param string $message 
     * @param array $attributes
     */
    public function error( $message, $attributes = array() )
    {
        $this->_customError = $message;
        $this->_errorAttributes = $attributes;
        
        return $this;
    }
    
    /**
     * Replace attribute string with attributes
     *  
     * @param string $string
     * @param array $attributes
     * @return string 
     */
    public function replaceAttributes( $string, $attributes )
    {
        $attributeString = '';
        
        foreach( $attributes as $name => $value )
        {
            if ( $name == 'value' ) continue;
            
            if ( $value === null )
            {
                $attributeString .= $name . ' ';
            }
            else
            {
                $attributeString .= $name . '="' . $value . '" ';
            }
        }
        
        return str_replace( '%attributes%', ( strlen( $attributeString ) > 0 ? ' ' : '' ) . substr( $attributeString, 0, -1 ), $string );
    }
    
    /**
     * Get element type
     * 
     * @return string
     */
    public function getType()
    {
        $array = explode( '_', get_class( $this ) );
        
        return strtolower( end( $array ) );
    }
    
    /**
     * Get all validators
     * 
     * @return array
     */
    public function getValidators()
    {
        return $this->_validators;
    }
    
    /**
     * Get all helpers
     * 
     * @return array
     */
    public function getHelpers()
    {
        return $this->_helpers;
    }
    
    /**
     * Get all element filters
     * 
     * @return array
     */
    public function getFilters()
    {
        return $this->_filters;
    }
    
    /**
     * Add required validator
     * 
     * @return Dmf_Form_Element 
     */
    public function isRequired()
    {
        return $this->addValidator( 'required' );
    }
    
    /**
     * Add an element validator
     * 
     * @param string $type
     * @param string $value
     * @throws Exception 
     * @return $this;
     */
    public function addValidator( $type, $value = null )
    {
        try
        {
            $class = 'Dmf_Form_Validator_' . ucfirst( $type );
            $this->_validators[] = new $class( $this->form, $this, $value );
        }
        catch( Exception $e )
        {
            throw new Exception( 'Validator: "' . $type . '" not found in form.' );
        }
        
        return $this;
    }
    
    /**
     * Add an element helper
     * 
     * @param string $type
     * @param mixed $value
     * @return Dmf_Form_Element
     * @throws Exception 
     */
    public function addHelper( $type, $value = null )
    {
        try
        {
            $class = 'Dmf_Form_Helper_' . ucfirst( $type );
            $this->_helpers[] = new $class( $this->form, $this, $value );
        }
        catch( Exception $e )
        {
            throw new Exception( 'Helper: "' . $type . '" not found in form.' );
        }
        
        return $this;
    }
    
    
    /**
     * Add an element validator
     * 
     * @param string $type
     * @throws Exception 
     * @return $this;
     */
    public function addFilter( $type )
    {
        try
        {
            $class = 'Dmf_Form_Filter_' . ucfirst( $type );
            $this->_filters[] = new $class;
        }
        catch( Exception $e )
        {
            throw new Exception( 'Filter: "' . $type . '" not found in form.' );
        }
        
        return $this;
    }
    
    /**
     * Add error to element
     * 
     * @param string $type
     * @param string $message 
     */
    public function addError( $type, $message )
    {
        $this->_errors[] = array(
            'type'      => $type,
            'message'   => $message
        );
    }
    
    /**
     * Get attribute
     * 
     * @param string $index
     * @return mixed
     */
    public function getAttr( $index )
    {
        return isset( $this->_attributes[ $index ] ) ? $this->_attributes[ $index ] : null;
    }
    
    /**
     * Add HTML text inserted before element
     * 
     * @param array $elements
     * @param boolean $append
     * @return Dmf_Form_Element 
     */
    public function addDecorators( $elements, $append = false )
    {
        if ( ! $append )
        {
            $this->_decorators = array_merge( $this->_decorators, $elements );
        }
        else
        {
            $this->_decorators = $elements;
        }
        
        return $this;
    }
    
    /**
     * Add a single decorator
     * 
     * @param Dmf_Form_Element $element
     * @param boolean $append 
     * @return Dmf_Form_Element 
     */
    public function addDecorator( $element, $append = false )
    {
        if ( is_array( $element ) )
        {
            throw new Exception( 'Element decorator is array, string expected.' );
        }
        
        $this->addDecorators( array( $element ), $append );
        
        return $this;
    }
    
    /**
     * Get content 
     */
    abstract function getContent();
    
    /**
     * Add label to element
     * 
     * @param string $value
     * @param array $attributes
     * @return mixed
     */
    public function setLabel( $value, $attributes = array() )
    {
        $label = new Dmf_Form_Element_Label( $this->form );
        $id = uniqid( rand( 0, 9999999 ) );
        
        $label->setValue( $value );
        $label->setAttrs( $attributes );
        if ( ! isset( $attributes['id'] ) && ! isset( $this->_attributes['id'] ) ) $label->setAttr( 'for', $id );
        
        $this->_label = $label;
        
        if ( ! isset( $attributes['id'] ) && ! isset( $this->_attributes['id'] ) ) $this->setAttr( 'id', $id );
        
        return $this;
    }
    
    /**
     * Set attribute -> Value
     * 
     * @param string $string
     * @return Dmf_Form_Element 
     */
    public function setValue( $string )
    {
        $this->setAttr( 'value', $string );
        
        return $this;
    }
    
    /**
     * Set attribute -> Name
     * 
     * @param string $name
     * @return Dmf_Form_Element 
     */
    public function setName( $name )
    {
        $this->setAttr( 'name', $name );
        
        return $this;
    }
    
    /**
     * Set attribute -> Action
     * 
     * @param string $action
     * @return Dmf_Form_Element 
     */
    public function setAction( $action )
    {
        $this->setAttr( 'action', $action );
        
        return $this;
    }
    
    
    
    /**
     * Set attribute
     * 
     * @param string $name
     * @param string $value
     * @return Dmf_Form_Element 
     */
    public function setAttr( $name, $value = null )
    {
        if ( $name == 'name' && strstr( $value, '[]' ) )
        {
            if ( ! isset( $this->form->_nameMultiple[ substr( $value, 0, -2 ) ] ) )
            {
                $this->form->_nameMultiple[ substr( $value, 0, -2 ) ] = 0;
            }
            else
            {
                $this->form->_nameMultiple[ substr( $value, 0, -2 ) ]++;
            }
            
            $value = substr( $value, 0, -2 ) . '[' . $this->form->_nameMultiple[ substr( $value, 0, -2 ) ] . ']';
        }
        
        $this->_attributes[ $name ] = $value;
        
        return $this;
    }
    
    /**
     * Set attributes
     * 
     * @param array $attributes 
     * @return Dmf_Form_Element
     */
    public function setAttrs( $attributes )
    {
        $this->_attributes = array_merge( $this->_attributes, (array)$attributes );
        
        return $this;
    }
    
    /**
     * Unset attribute
     * 
     * @param string $name
     * @return Dmf_Form_Element 
     */
    public function unsetAttr( $name )
    {
        $this->_attributes[ $name ] = null;
        $this->_attributes = array_filter( $this->_attributes );
        
        return $this;
    }
    
    /**
     * Render all form decorators
     * 
     * @param string $renderContent
     * @return string
     */
    public function renderDecorators( $renderContent )
    {
        if ( count( $this->_decorators ) == 0 )
        {
            return $renderContent;
        }
       
        foreach( array_reverse( $this->_decorators ) as $name => $attributes )
        {
            if ( is_numeric( $name ) )
            {
                $name = $attributes;
                $attributes = array();
            }
            
            $element = ( $name instanceof Dmf_Form_Element ? $name : $this->form->element( $name ) );
            
            $element->setAttrs( $attributes );
            
            if ( $this instanceof Dmf_Form_Element_Form )
            {
                $element->setAttr( 'value', $this->getAttr( 'value' ) );
                
                unset( $this->_attributes['value'] );
                
                $this->setAttr( 'value', $element->render() );
                continue;
            }
            else
            {
                $element->setAttr( 'value', PHP_EOL . $element->getAttr( 'value' ) . $renderContent );
            }
            
            $renderContent = $element->render();
        }
        
        if ( $this instanceof Dmf_Form_Element_Form )
        {
            $form = $this->form->element( 'form' );
            $form->setAttrs( $this->_attributes );
            
            $renderContent = $form->render();
        }
        
        return $renderContent;
    }
    
    /**
     * Check if element has error
     * 
     * @return bool
     */
    public function hasError()
    {
        return ( count( $this->_errors ) > 0 );
    }
    
    /**
     * Render the element and return HTML content
     * 
     * @return string 
     */
    public function render()
    {
        $content = $this->getContent(); //Execute this method first to add default attributes
        $result = '';
        
        if ( $this->_label instanceof Dmf_Form_Element_Label )
        {
            $result .= $this->_label->render() . PHP_EOL;
        }
        
        if ( $this->hasError() )
        {
            $this->setAttrs( $this->_errorAttributes );
        }
        
        $result .= $this->_prependText;
        $result .= PHP_EOL . $this->replaceAttributes( $content, $this->_attributes );
        if ( ! is_array( $this->getAttr( 'value' ) ) ) $result = str_replace( '%value%', $this->getAttr( 'value' ), $result );
        if ( ! is_array( $this->getAttr( 'value' ) ) ) $result = str_replace( '%value_escaped%', htmlentities( $this->getAttr( 'value' ) ), $result );
        $result .= $this->_appendText;
        
        if ( $this->_customError === null )
        {
            foreach( $this->_errors as $error )
            {
                $result .= $error['message'];
            }
        }
        else
        {
            foreach( $this->_errors as $error )
            {
                $result .= $this->_customError;
                break;
            }
        }
        
        return $this->renderDecorators( $result );
    }
    
    /**
     * Add default attribute that can be overwritten
     * 
     * @param string $name
     * @param string $value 
     */
    public function addDefaultAttribute( $name, $value )
    {
        if ( ! isset( $this->_attributes[ $name ] ) )
        {
            $this->_attributes[ $name ] = $value;
        }
    }
    
    /**
     * I'm very very lazy.. 
     * Because in addElement we can use this Element as string withouth validating.
     * 
     * @return string
     */
    public function __toString()
    {
        return (string)null;
    }
}